package com.th.switchnet;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

/**
 * 目录扫描，找到 apk ， 拷贝，解压，替换内容，压缩，再拷贝回去
 * 
 * @author thduan
 * @date 2015-11-11 下午2:14:14
 */
public class SwitchLogic {
//	private static int BUFFER_SIZE = 4096 * 1024; //修改propertities文件耗时 73s
	//这个速度 比 BUFFER_SIZE 4096 * 1024 快多了， why? 
	private static int BUFFER_SIZE = 4 * 1024; //耗时 22s【首次修改耗时45s，原因暂未知】
//	private static int BUFFER_SIZE = 1 * 1024; //耗时 53s
	private final static int HANDLER_SUCCESS = 1;
	private final static int HANDLER_FAILED = 2;
	private final static int HANDLER_STATUS_CHANGE = 3;
	
	private String LOG_TAG = "SwitchNet";
	private ExecutorService service;
	private Context context;
	private IEditNetListener listener;
	
	public SwitchLogic(Context context, IEditNetListener listener) {
		this.listener = listener;
		this.context = context;
		service = Executors.newFixedThreadPool(2);
	}

	public void editNet(final String netValue) {
		service.submit(new Runnable() {
			
			@Override
			public void run() {
				long start = System.currentTimeMillis();
				
				editNetLogic(netValue);
				
				long end = System.currentTimeMillis();
				long duraMillis = end - start;
				long secs = duraMillis / 1000;
				showStatus("用时：" + secs + " 秒");
			}
		});
	}
	
	private Handler uiHandler = new Handler(Looper.getMainLooper()) {
		@Override
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case HANDLER_SUCCESS:
				if(listener !=null) {
					listener.onSuccess();
				}
				break;
			case HANDLER_FAILED:
				if(listener != null) {
					listener.onFailed(msg.obj.toString());
				}
				break;
			case HANDLER_STATUS_CHANGE:
				if(listener != null) {
					listener.onStatusChange(msg.obj.toString());
				}
				break;
			default:
				break;
			}
		};
	};
	
	private void postStatusMsg(String status) {
		Message msg = uiHandler.obtainMessage();
		msg.what = HANDLER_STATUS_CHANGE;
		msg.obj = status;
		uiHandler.sendMessage(msg);
	}
	
	private void postSuccessMsg() {
		Message msg = uiHandler.obtainMessage(HANDLER_SUCCESS);
		uiHandler.sendMessage(msg);
	}
	
	private void postFailedMsg(String errMsg) {
		Message msg = uiHandler.obtainMessage(HANDLER_FAILED);
		msg.obj = errMsg;
		uiHandler.sendMessage(msg);
	}
	
	/**
	 * 修改网络环境
	 * @param netValue
	 * @return
	 */
	public boolean editNetLogic(String netValue) {
		prepare(netValue);
		try {
			appPermission();
			File xmFile = findFile(AppDefine.APK_DIR, AppDefine.APK_PREFIX);
			if (xmFile != null) {
				showStatus("找到原文件：" + xmFile.getAbsolutePath());
				// apk 在 /data/app 中的路径
				String appDirApkPath = xmFile.getAbsolutePath();
				// apk 在sdcard中修改前的路径
				String sdApkPath = AppDefine.CACHE_DIR + xmFile.getName();
				// apk 在sdcard中修改后的路径
				String sdApkPathAfter = sdApkPath + "_after";
				// 拷贝：从 /data/app 目录 拷贝到sdcard
				appPermission();
				showStatus("开始 从 /data/app 目录 拷贝到sdcard……");
				copyFileByCmd(appDirApkPath, sdApkPath);
				showStatus("拷贝结束");
				// 改写 propertities文件内容
				showStatus("开始修改 propertities文件内容……");
				changePropertiesFile(sdApkPath, sdApkPathAfter, netValue);
				showStatus("propertities 修改完成");
				// 复制到app目录
				appPermission(); // 虽然程序刚开始获得 /data/app 的读写权限，但到这可能已经没有读写权限了，所以此处加上
				showStatus("从sdcard拷贝修改后的到 /data/app……");
				copyFileByCmd(sdApkPathAfter, appDirApkPath);
				showStatus("拷贝结束");
				postSuccessMsg();
			}else {
				showStatus("没找到原文件");
				postFailedMsg("没找到原文件");
			}
			return true;
		} catch (final Exception e) {
			e.printStackTrace();
			postFailedMsg(e.getMessage());
		}
		return false;
	}
	
	private void prepare(String netValue) {
		showStatus("开始，目标内容：" + netValue);
		mkDir(AppDefine.CACHE_DIR);
		delFolder(AppDefine.CACHE_DIR);
	}
	
	private void showStatus(String msg) {
		LogUtil.log(msg);
		postStatusMsg(msg);
	}

	/**
	 * 使访问 /data/app 权限获得
	 */
	private void appPermission() {
		execCmd("chmod 777 /data/app", false);
		try {
			//执行上述命令后，界面会弹出需要授权的操作，必须点击允许
			//此处等待2秒，允许操作能完成，如果自己觉得2秒短了，怕自己反应不过来，那么可以设置长一点
			Thread.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * shell命令拷贝文件
	 * 
	 * @param srcFileP
	 * @param targetFileP
	 */
	private void copyFileByCmd(String srcFileP, String targetFileP) {
		execCmd(createCopyCmd(srcFileP, targetFileP), false);
	}

	private String createCopyCmd(String srcFileP, String targetFileP) {
		return "dd if=" + srcFileP + " of=" + targetFileP;
	}

	/**
	 * 删除文件
	 * 
	 * @param filePath
	 */
	private void delFile(String filePath) {
		File file = new File(filePath);
		if (file.exists()) {
			file.delete();
		}
	}

	/**
	 * 从 /data/app 目录下 找到 指定包文件
	 * 
	 * @param dir
	 * @param filePrefix
	 * @return
	 */
	private File findFile(String dir, String filePrefix) {
		File file = new File(dir);
		File[] files = file.listFiles();
		File searchFile = null;
		for (File mFile : files) {
			if (mFile.getName().startsWith(filePrefix)) {
				searchFile = mFile;
				break;
			}
		}
		return searchFile;
	}

	private String getFileNameWithoutExt(String fileName) {
		return fileName.substring(0, fileName.lastIndexOf("."));
	}

	private void copyFile(String orgFile, String targetFile) throws Exception {
		copyFile(new File(orgFile), targetFile);
	}

	/**
	 * 文件拷贝
	 * 
	 * @param file
	 * @param targetFile
	 * @return
	 */
	private void copyFile(File file, String targetFile) throws Exception {
		if (file.isFile() && file.exists()) {
			InputStream is = new FileInputStream(file);
			FileOutputStream fos = new FileOutputStream(targetFile);
			byte[] buffer = new byte[1024];
			int readLen = 0;
			while ((readLen = is.read(buffer)) != -1) {
				fos.write(buffer, 0, readLen);
				fos.flush();
			}
			is.close();
			fos.close();
		}
	}

	/**
	 * 解压文件
	 * 
	 * @param zipFileString
	 * @param outPathString
	 * @throws Exception
	 */
	private void UnZipFolder(String zipFileString, String outPathString)
			throws Exception {
		ZipInputStream inZip = new ZipInputStream(new FileInputStream(
				zipFileString));
		ZipEntry zipEntry;
		String szName = "";
		while ((zipEntry = inZip.getNextEntry()) != null) {
			szName = zipEntry.getName();
			if (zipEntry.isDirectory()) {
				// get the folder name of the widget
				szName = szName.substring(0, szName.length() - 1);
				File folder = new File(outPathString + File.separator + szName);
				folder.mkdirs();
			} else {
				File file = new File(outPathString + File.separator + szName);
				File parent = file.getParentFile();
				if (parent != null && !parent.exists()) {
					parent.mkdirs();
				}
				file.createNewFile();
				// get the output stream of the file
				FileOutputStream out = new FileOutputStream(file);
				int len;
				byte[] buffer = new byte[1024];
				// read (len) bytes into buffer
				while ((len = inZip.read(buffer)) != -1) {
					// write (len) byte from buffer at the position 0
					out.write(buffer, 0, len);
					out.flush();
				}
				out.close();
			}
		}
		inZip.close();
	}

	/**
	 * 修改 propertities文件，too slow
	 * 
	 * @param srcZipFile
	 * @param targetZipFile
	 */
	private void changePropertiesFile(String srcZipFp, String targetZipFp,
			String netValue) throws Exception {
		ZipFile srcZipFile = new ZipFile(srcZipFp);
		ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(
				targetZipFp));
		Enumeration<? extends ZipEntry> entries = srcZipFile.entries();
		while (entries.hasMoreElements()) {
			ZipEntry e = entries.nextElement();
			if (!e.isDirectory()) {
				if (e.getName().contains(AppDefine.PRO_FILE_NAME)) {
					// target file, 将新文件写入，老文件丢弃
					ZipEntry zipEntry = new ZipEntry(e.getName());
					zipOut.putNextEntry(zipEntry);
					zipOut.write(netValue.getBytes());
				} else {
					zipOut.putNextEntry(e);
					copyZipEntry(srcZipFile.getInputStream(e), zipOut);
				}
			} else {
				zipOut.putNextEntry(e);
			}
			zipOut.closeEntry();
		}
		srcZipFile.close();
		zipOut.close();
	}
	
	/**
	 * zip包中指定文件的内容 替换为 content
	 * PS：zip4j 修改的apk文件，无法重新启动，报找不到 applicaton 或 其他类
	 * @param zipFile
	 * @param targetFilePath
	 * @param content
	 */
	public void changeTargetFileContent(String zipFilePath, String content) throws Exception {
		//生成新文件
		String newFilePath = AppDefine.CACHE_DIR + AppDefine.PRO_FILE_NAME;
		delFile(newFilePath);
		FileOutputStream out = new FileOutputStream(newFilePath);
		out.write(content.getBytes());
		out.close();
		
		//add file
		net.lingala.zip4j.core.ZipFile zipFile = new net.lingala.zip4j.core.ZipFile(zipFilePath);
		ZipParameters parameters = new ZipParameters();
		parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
		parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
		parameters.setRootFolderInZip(AppDefine.PRO_FILE_DIR);
		
		//remove file
		zipFile.removeFile("res/raw/ktv.properties");
		
		//add file 
		zipFile.addFile(new File(newFilePath), parameters);
	}
	
	private void getZipList(String zipFile) {
		net.lingala.zip4j.core.ZipFile zip;
		try {
			zip = new net.lingala.zip4j.core.ZipFile(zipFile);
//			zip.add
			List fileHeaderList = zip.getFileHeaders();
			for(int i = 0; i < fileHeaderList.size(); i++) {
				FileHeader fileHeader = (FileHeader) fileHeaderList.get(i);
				if(fileHeader.getFileName().contains(AppDefine.PRO_FILE_NAME)) {
					String fn = fileHeader.getFileName();
					String b = fn;
				}
			}
			
			
		} catch (ZipException e) {
			e.printStackTrace();
		}
		
	}

	public void copyZipEntry(InputStream input, OutputStream output)
			throws IOException {
		int bytesRead;
		byte[] BUFFER = new byte[BUFFER_SIZE];
		while ((bytesRead = input.read(BUFFER)) != -1) {
			output.write(BUFFER, 0, bytesRead);
		}
	}

	private void ZipFolder(String srcFileString, String zipFileString)
			throws Exception {
		// create ZIP
		ZipOutputStream outZip = new ZipOutputStream(new FileOutputStream(
				zipFileString));
		// create the file
		File file = new File(srcFileString);
		// compress
		ZipFiles(file.getParent() + File.separator, file.getName(), outZip);
		// finish and close
		outZip.finish();
		outZip.close();
	}

	/**
	 * compress files
	 * 
	 * @param folderString
	 * @param fileString
	 * @param zipOutputSteam
	 * @throws Exception
	 */
	private static void ZipFiles(String folderString, String fileString,
			ZipOutputStream zipOutputSteam) throws Exception {
		if (zipOutputSteam == null)
			return;
		File file = new File(folderString + fileString);
		if (file.isFile()) {
			ZipEntry zipEntry = new ZipEntry(fileString);
			FileInputStream inputStream = new FileInputStream(file);
			zipOutputSteam.putNextEntry(zipEntry);
			int len;
			byte[] buffer = new byte[4096];
			while ((len = inputStream.read(buffer)) != -1) {
				zipOutputSteam.write(buffer, 0, len);
			}
			zipOutputSteam.closeEntry();
		} else {
			// folder
			String fileList[] = file.list();
			// no child file and compress
			if (fileList.length <= 0) {
				ZipEntry zipEntry = new ZipEntry(fileString + File.separator);
				zipOutputSteam.putNextEntry(zipEntry);
				zipOutputSteam.closeEntry();
			}
			// child files and recursion
			for (int i = 0; i < fileList.length; i++) {
				ZipFiles(folderString, fileString + java.io.File.separator
						+ fileList[i], zipOutputSteam);
			}// end of for
		}
	}

	/**
	 * 写文件
	 * 
	 * @param fileName
	 * @param write_str
	 * @throws IOException
	 */
	public void writeSDFile(String fileName, String write_str)
			throws IOException {

		File file = new File(fileName);
		file.deleteOnExit();
		FileOutputStream fos = new FileOutputStream(file);

		byte[] bytes = write_str.getBytes();

		fos.write(bytes);

		fos.close();
	}

	/**
	 * 执行终端 命令， adb shell
	 * 
	 * @param cmd
	 *            : 命令
	 * @param haveOutStream
	 *            ： 是否有输出
	 * @return
	 */
	private String execCmd(String cmd, boolean haveOutStream) {
		String result = null;
		Runtime mRuntime = Runtime.getRuntime();
		DataOutputStream os = null;
		try {
			// 用 su 用户身份执行命令 才有效
			Process mProcess = mRuntime.exec("su");
			os = new DataOutputStream(mProcess.getOutputStream());
			os.writeBytes(cmd + "\n");
			os.writeBytes("exit\n"); // 必须加这句，否则卡住
			os.flush();
			mProcess.waitFor();

			if (haveOutStream) { // 没有输出则不读取，否则会被卡住
				BufferedReader mReader = new BufferedReader(
						new InputStreamReader(mProcess.getInputStream()));
				StringBuffer mRespBuff = new StringBuffer();
				char[] buff = new char[1024];
				int ch = 0;
				while ((ch = mReader.read(buff)) != -1) {
					mRespBuff.append(buff, 0, ch);
				}
				mReader.close();
				result = mRespBuff.toString();
			}
			Log.i(LOG_TAG, "exec shell over: " + cmd + ", out: " + result);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return result;
	}
	
	public void testPro(String netV) {
		try {
			changeTargetFileContent(AppDefine.CACHE_DIR+ "com.iflytek.xmmusic.activitys-1.apk", netV);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 创建目录
	 * @param dir
	 */
	private void mkDir(String dir) {
		File directory = new File(dir);
		if(!directory.exists()) {
			directory.mkdirs();
		}
	}
	
	/**
	 * 删除目录下的文件
	 * PS: 只删除一级文件，不考虑子目录
	 * @param dir
	 */
	private void delFolder(String dir) {
		File directory = new File(dir);
		if(directory.exists() && directory.isDirectory()) {
			File[] files = directory.listFiles();
			if(files != null) {
				for(int i = 0; i < files.length; i++) {
					files[i].delete();
				}
			}
		}
	}
	
}
